﻿/////////////////////////////////////////////////////////////////
//
// Physics Helper XAML
//
// by Andy Beaulieu - http://www.andybeaulieu.com
//
// LICENSE: This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS
// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
/////////////////////////////////////////////////////////////////
using System;
using System.Net;
using System.Collections.Generic;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using xna = Microsoft.Xna.Framework;
using Windows.UI.Xaml.Controls;
using SysWin = Windows.Foundation;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Imaging;
using UIShapes = Windows.UI.Xaml.Shapes;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Common.ConvexHull;
using FarseerPhysics.Factories;
using Windows.Graphics.Imaging;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using System.Linq;

namespace Spritehand.FarseerHelper
{
    public class BoundaryHelperBox2d
    {
        Canvas _cnvGame;

        public enum DecomposerTypes
        {
            Bayazit,
            CDT,
            Earclip,
            Flipcode,
            Melkman,
            Giftwrap
        }

        public static DecomposerTypes DecomposerType { get; set; }

        public static double pixelsPerMeter = 100;
        public static xna.Vector2 ScreenTopLeft { get; set; }
        public static xna.Vector2 ScreenBottomRight { get; set; }

        public static xna.Vector2 WorldTopLeft { get; set; }
        public static xna.Vector2 WorldBottomRight { get; set; }

        public BoundaryHelperBox2d(Canvas cnvGame)
        {
            _cnvGame = cnvGame;

            // TODO: tweak margin/etc of Canvas

            if (Double.IsNaN(_cnvGame.Width) || Double.IsNaN(_cnvGame.Height))
            {
                if (Double.IsNaN(_cnvGame.ActualWidth) || Double.IsNaN(_cnvGame.ActualHeight)
                    || _cnvGame.ActualWidth == 0 || _cnvGame.ActualHeight == 0)
                {
                    _cnvGame.Width = 1366;
                    _cnvGame.Height = 768;
                }
                else
                {
                    _cnvGame.Width = _cnvGame.ActualWidth;
                    _cnvGame.Height = _cnvGame.ActualHeight;
                }
                System.Diagnostics.Debug.WriteLine("Your game canvas did not specify a width and height, so it was forced to 1366x768.");
            }

            ScreenTopLeft = new xna.Vector2(0, 0);
            ScreenBottomRight = new xna.Vector2((float)_cnvGame.Width, (float)_cnvGame.Height);

            xna.Vector2 screenSize = new xna.Vector2((float)cnvGame.Width, (float)cnvGame.Height);
            float ratio = screenSize.X / screenSize.Y;
            float halfWorldSize = (float)GetWorldSize(screenSize) / 2.0f;
            xna.Vector2 extent = new xna.Vector2(-ratio * halfWorldSize, halfWorldSize);

            WorldTopLeft = extent;
            WorldBottomRight = -extent;
        }

        private double GetWorldSize(xna.Vector2 screenSize)
        {
            double worldSize = 25;
            worldSize = screenSize.X / pixelsPerMeter;
            return worldSize;
        }

        public async Task<List<Shape>> CreateShapesFromElement(World simulator, PhysicsSprite entity, PhysicsSprite.ShapeTypes shapeType)
        {
            List<Shape> shapeDefs = new List<Shape>();
            Vertices vertices;
            List<Vertices> verticesDecomposed;

            switch (shapeType)
            {
                case PhysicsSprite.ShapeTypes.Image:

                    Image img = entity.Children[0] as Image;

                    byte[] sourcePixels = entity._imgPixelData;
                    SysWin.Size imgSize = entity._imgSize;
                    uint[] data = new uint[sourcePixels.Length / 4];
                    Buffer.BlockCopy(sourcePixels, 0, data, 0, sourcePixels.Length);

                    int imgWidth = Convert.ToInt32(imgSize.Width); // Convert.ToInt32(Double.IsNaN(img.Width) ? entity.Width : img.Width);
                    vertices = PolygonTools.CreatePolygon(data, Convert.ToInt32(imgWidth), false);

                    Vertices verticesToWorld = new Vertices();
                    for (var i = 0; i < vertices.Count; i++)
                    {
                        xna.Vector2 vecToWorld = ScreenSizeToWorldSize(new SysWin.Size(vertices[i].X, vertices[i].Y));
                        verticesToWorld.Add(new xna.Vector2(vecToWorld.X, vecToWorld.Y));
                    }

                    // translate by centroid
                    xna.Vector2 centroid = -verticesToWorld.GetCentroid();
                    verticesToWorld.Translate(ref centroid);

                    var vecScale = new xna.Vector2((float)(entity.Width / imgSize.Width), (float)(entity.Height / imgSize.Height));
                    verticesToWorld.Scale(ref vecScale);
                    
                    // translate by half image size
                    //xna.Vector2 halfSize = new xna.Vector2((float)(entity.Width / 2f), (float)(entity.Height) / 2f);
                    //xna.Vector2 halfSizeToWorld = ScreenToWorld(halfSize);
                    //halfSizeToWorld.X = -halfSizeToWorld.X;
                    //halfSizeToWorld.Y = -halfSizeToWorld.Y;
                    //verticesToWorld.Translate(halfSizeToWorld);


                    //PolygonShape imagePolygon = new PolygonShape(0.1f);
                    //imagePolygon.Set(verticesToWorld);
                    //shapeDefs.Add(imagePolygon);

                    switch (DecomposerType)
                    {
                        case DecomposerTypes.CDT:
                            verticesDecomposed = CDTDecomposer.ConvexPartition(verticesToWorld);
                            break;
                        case DecomposerTypes.Earclip:
                            verticesDecomposed = EarclipDecomposer.ConvexPartition(verticesToWorld);
                            break;
                        case DecomposerTypes.Flipcode:
                            verticesDecomposed = FlipcodeDecomposer.ConvexPartition(verticesToWorld);
                            break;
                        case DecomposerTypes.Melkman:
                            verticesDecomposed = new List<Vertices>();
                            verticesDecomposed.Add(Melkman.GetConvexHull(verticesToWorld));
                            break;
                        case DecomposerTypes.Giftwrap:
                            verticesDecomposed = new List<Vertices>();
                            verticesDecomposed.Add(GiftWrap.GetConvexHull(verticesToWorld));
                            break;
                        default:
                            verticesDecomposed = BayazitDecomposer.ConvexPartition(verticesToWorld);
                            break;
                    }


                    foreach (Vertices vertexDecomposed in verticesDecomposed)
                    {
                        if (vertexDecomposed.Count < 3)
                        {
                            //throw new Exception("The PhysicsSprite " + entity.Name + " contains a Path as the first element, but one of the Decomposed Polygons has " + vertexDecomposed.Count + " vertices. You must have at least 3 vertices for a polygon boundary.");
                        }
                        else
                        {
                            if (vertexDecomposed.Count > FarseerPhysics.Settings.MaxPolygonVertices)
                            {
                                // trim down the # of vertices
                                double skipNum = (vertexDecomposed.Count - FarseerPhysics.Settings.MaxPolygonVertices);
                                skipNum = Math.Round(Convert.ToDouble(vertexDecomposed.Count) / skipNum);


                                for (int i = vertexDecomposed.Count - 1; i >= 0; i -= Convert.ToInt32(skipNum))
                                {
                                    vertexDecomposed.Remove(vertexDecomposed[i]);
                                }
                            }

                            PolygonShape polygonDef = new PolygonShape(0.1f);
                            polygonDef.Set(vertexDecomposed);
                            shapeDefs.Add(polygonDef);
                        }
                    }




                    break;

                case PhysicsSprite.ShapeTypes.Ellipse:
                    xna.Vector2 worldRadius = ScreenSizeToWorldSize(new SysWin.Size(entity.Width / 2, entity.Width / 2));

                    CircleShape circleDef = new CircleShape(worldRadius.Y, 0.1f);

                    shapeDefs.Add(circleDef);
                    break;
                case PhysicsSprite.ShapeTypes.Rectangle:
                    PolygonShape polygonRect = new PolygonShape(0.1f);
                    SysWin.Rect rect = new SysWin.Rect(0, 0, entity.Width, entity.Height);

                    xna.Vector2 pos = new xna.Vector2(0F, 0F);
                    // does the box have a rotation?
                    double angle = 0;
                    if (entity.RenderTransform is RotateTransform)
                    {
                        angle = (entity.RenderTransform as RotateTransform).Angle;
                    }
                    else
                        if (entity.RenderTransform is CompositeTransform)
                    {
                        angle = (entity.RenderTransform as CompositeTransform).Rotation;
                    }

                    float radAngle = (float)(Math.PI * angle / 180.0);

                    xna.Vector2 worldSize = ScreenSizeToWorldSize(new SysWin.Size(rect.Width / 2, rect.Height / 2));
                    //polygonRect.SetAsBox(worldSize.X, worldSize.Y, new xna.Vector2(0,0), radAngle);
                    polygonRect.SetAsBox(worldSize.X, worldSize.Y, new xna.Vector2(0, 0), 0);
                    shapeDefs.Add(polygonRect);
                    break;
                default:
                    // Polygon via Path
                    vertices = new Vertices();

                    UIShapes.Path pathBoundary = (entity as Canvas).Children[0] as UIShapes.Path;
                    if (pathBoundary == null)
                    {
                        throw new Exception("The PhysicsSprite " + entity.Name + " has a Polygon ShapeType, but the first element in the PhysicsSprite must be a Path element that depicts the shape. Note you can set the Visibility of this Path to Collapsed if you do not want it shown in the Sprite.");
                    }
                    else
                    {
                        PathGeometry pathGeom = pathBoundary.Data as PathGeometry;

                        if (pathGeom == null)
                        {
                            throw new Exception("The PhysicsSprite " + entity.Name + " contains a Path as the first element, but that Path has no PathGeometry defined.");
                        }
                        else
                        {
#if WINDOWS_PHONE || SILVERLIGHT
                            PathConverter.StringToPathGeometryConverter cnv = new PathConverter.StringToPathGeometryConverter();
                            string geom = pathGeom.ToString();
                            if (geom != string.Empty)
                            {
                                pathGeom = cnv.Convert(geom);
                            }
                            
#endif
                            foreach (PathFigure figure in pathGeom.Figures)
                            {
                                SysWin.Point ptStart = figure.StartPoint;
                                // adjust the position for origin at center
                                ptStart.X = (ptStart.X - ((float)entity.Width / 2));
                                ptStart.Y = (ptStart.Y - ((float)entity.Height / 2));
                                // adjust the position for Canvas Left, Top
                                ptStart.X = ptStart.X + Canvas.GetLeft(pathBoundary);
                                ptStart.Y = ptStart.Y + Canvas.GetTop(pathBoundary);

                                vertices.Add(ScreenSizeToWorldSize(ptStart));

                                foreach (PathSegment segment in figure.Segments)
                                {
                                    if (segment is LineSegment)
                                    {
                                        LineSegment lineSegment = segment as LineSegment;
                                        SysWin.Point ptNext = lineSegment.Point;
                                        // adjust the position for origin at center
                                        ptNext.X = (ptNext.X - ((float)entity.Width / 2));
                                        ptNext.Y = (ptNext.Y - ((float)entity.Height / 2));
                                        // adjust the position for Canvas Left, Top
                                        ptNext.X = ptNext.X + Canvas.GetLeft(pathBoundary);
                                        ptNext.Y = ptNext.Y + Canvas.GetTop(pathBoundary);
                                        vertices.Add(ScreenSizeToWorldSize(ptNext));
                                    }
                                    else
                                        if (segment is BezierSegment)
                                    {
                                        // HACK: This DOES NOT take into account a real Bezier Curve!!
                                        BezierSegment bezSegment = segment as BezierSegment;

                                        SysWin.Point ptNext = bezSegment.Point3;
                                        // adjust the position for origin at center
                                        ptNext.X = (ptNext.X + ((float)entity.Width / 2));
                                        ptNext.Y = (ptNext.Y + ((float)entity.Height / 2));
                                        // adjust the position for Canvas Left, Top
                                        ptNext.X = ptNext.X + Canvas.GetLeft(pathBoundary);
                                        ptNext.Y = ptNext.Y + Canvas.GetTop(pathBoundary);
                                        vertices.Add(ScreenSizeToWorldSize(bezSegment.Point2));

                                    }
                                }
                            }



                            // DEMO: (2): We need CONVEX shapes, but Decomposers are provided in Farseer.
                            switch (DecomposerType)
                            {
                                case DecomposerTypes.CDT:
                                    verticesDecomposed = CDTDecomposer.ConvexPartition(vertices);
                                    break;
                                case DecomposerTypes.Earclip:
                                    verticesDecomposed = EarclipDecomposer.ConvexPartition(vertices);
                                    break;
                                case DecomposerTypes.Flipcode:
                                    verticesDecomposed = FlipcodeDecomposer.ConvexPartition(vertices);
                                    break;
                                case DecomposerTypes.Melkman:
                                    verticesDecomposed = new List<Vertices>();
                                    verticesDecomposed.Add(Melkman.GetConvexHull(vertices));
                                    break;
                                case DecomposerTypes.Giftwrap:
                                    verticesDecomposed = new List<Vertices>();
                                    verticesDecomposed.Add(GiftWrap.GetConvexHull(vertices));
                                    break;
                                default:
                                    verticesDecomposed = BayazitDecomposer.ConvexPartition(vertices);
                                    break;
                            }


                            foreach (Vertices vertexDecomposed in verticesDecomposed)
                            {
                                if (vertexDecomposed.Count < 3)
                                {
                                    //throw new Exception("The PhysicsSprite " + entity.Name + " contains a Path as the first element, but one of the Decomposed Polygons has " + vertexDecomposed.Count + " vertices. You must have at least 3 vertices for a polygon boundary.");
                                }
                                else
                                {
                                    if (vertexDecomposed.Count > FarseerPhysics.Settings.MaxPolygonVertices)
                                    {
                                        // trim down the # of vertices
                                        double skipNum = (vertexDecomposed.Count - FarseerPhysics.Settings.MaxPolygonVertices);
                                        skipNum = Math.Round(Convert.ToDouble(vertexDecomposed.Count) / skipNum);


                                        for (int i = vertexDecomposed.Count - 1; i >= 0; i -= Convert.ToInt32(skipNum))
                                        {
                                            vertexDecomposed.Remove(vertexDecomposed[i]);
                                        }
                                    }

                                    PolygonShape polygonDef = new PolygonShape(0.1f);
                                    polygonDef.Set(vertexDecomposed);
                                    shapeDefs.Add(polygonDef);
                                }
                            }
                        }
                    }

                    break;
            }


            return shapeDefs;
        }


        public xna.Vector2 ScreenToWorld(xna.Vector2 screen)
        {
            return ConvertCoordinates((float)screen.X, (float)screen.Y, ScreenTopLeft, ScreenBottomRight, WorldTopLeft, WorldBottomRight, true);
        }

        public xna.Vector2 WorldToScreen(xna.Vector2 world)
        {
            xna.Vector2 value = ConvertCoordinates(world.X, world.Y, WorldTopLeft, WorldBottomRight, ScreenTopLeft, ScreenBottomRight, true);

            return new xna.Vector2(value.X, value.Y);
        }

        public xna.Vector2 ScreenSizeToWorldSize(SysWin.Size size)
        {
            return ConvertSize(new xna.Vector2((float)size.Width, (float)size.Height), ScreenTopLeft, ScreenBottomRight, WorldTopLeft, WorldBottomRight, true);
        }

        public xna.Vector2 ScreenSizeToWorldSize(SysWin.Point size)
        {
            return ConvertSize(new xna.Vector2((float)size.X, (float)size.Y), ScreenTopLeft, ScreenBottomRight, WorldTopLeft, WorldBottomRight, true);
        }

        public xna.Vector2 WorldSizeToScreenSize(xna.Vector2 size)
        {
            xna.Vector2 result = ConvertSize(size, WorldTopLeft, WorldBottomRight, ScreenTopLeft, ScreenBottomRight, true);
            return result;
        }

        private float ScreenWidth
        {
            get { return (ScreenBottomRight - ScreenTopLeft).X; }
        }

        private float ScreenHeight
        {
            get { return (ScreenBottomRight - ScreenTopLeft).Y; }
        }

        private xna.Vector2 ConvertCoordinates(float x, float y, xna.Vector2 topLeft1, xna.Vector2 bottomRight1, xna.Vector2 topLeft2, xna.Vector2 bottomRight2, bool invertY)
        {
            float w1 = bottomRight1.X - topLeft1.X;
            float h1 = bottomRight1.Y - topLeft1.Y;
            float u = (x - topLeft1.X) / w1;
            float v = (y - topLeft1.Y) / h1;
            if (invertY)
            {
                v = 1 - v;
            }
            xna.Vector2 p = new xna.Vector2();
            p.X = ((1.0f - u) * topLeft2.X) + (u * bottomRight2.X);
            p.Y = ((1.0f - v) * topLeft2.Y) + (v * bottomRight2.Y);
            return p;
        }

        private xna.Vector2 ConvertSize(xna.Vector2 size, xna.Vector2 topLeft1, xna.Vector2 bottomRight1, xna.Vector2 topLeft2, xna.Vector2 bottomRight2, bool invertY)
        {
            float w1 = bottomRight1.X - topLeft1.X;
            float h1 = bottomRight1.Y - topLeft1.Y;
            float w2 = bottomRight2.X - topLeft2.X;
            float h2 = bottomRight2.Y - topLeft2.Y;
            float rw = (w2 / w1);
            float rh = (invertY ? -1 : 1) * (h2 / h1);
            xna.Vector2 p = new xna.Vector2();
            p.X = size.X * rw;
            p.Y = size.Y * rh;
            return p;
        }



        public static void SplitCircle(CircleShape circle, out Vertices first, out Vertices second)
        {
            const int numSegmentsPerHalf = 4;

            first = new Vertices();
            first.Add(circle.Position);
            SysWin.Point ptCenter = new SysWin.Point(circle.Position.X, circle.Position.Y);

            for (int i = 0; i < numSegmentsPerHalf; i++)
            {
                double angle = i * (180 / numSegmentsPerHalf);
                SysWin.Point pt = PhysicsUtilities.GetPointFromDistanceAngle(angle, circle.Radius, ptCenter);
                first.Add(new xna.Vector2((float)pt.X, (float)pt.Y));
            }

            second = new Vertices();
            second.Add(circle.Position);

            for (int i = 0; i < numSegmentsPerHalf; i++)
            {
                double angle = i * (180 / numSegmentsPerHalf);
                SysWin.Point pt = PhysicsUtilities.GetPointFromDistanceAngle(angle, circle.Radius, ptCenter);
                second.Add(new xna.Vector2((float)pt.X, (float)pt.Y));
            }
        }


    }



}

